#!/usr/bin/env python3
# -*- coding: utf-8 -*-


from public.define import *
from public.util import is_none

consts = {
    'normal_element_type': 0,   # 普通元素
    'check_element_type': 1,    # 校验元素
    'store_element_type': 2,    # 存储元素，历史原因已经移除
    'store_unit_type': 3        # 嵌套存储单元名称
}


# 对元素进行trim处理
def format_element(element):
    if not isinstance(element, str):
        return element
    result = element.strip()
    idx = result.find('\t')
    if idx >= 0:
        result = result.replace('\t', '')
    idx = result.find('\n')
    if idx >= 0:
        result = result.replace('\n', '')
    idx = result.find('\'')
    if idx >= 0:
        result = result.replace('\'', '^')
    idx = result.find('\"')
    if idx >= 0:
        result = result.replace('\"', '')
    return result.strip()


# 将xpath解析出来的结果根据action进行加工
def act_element_value(task, action, target):
    if action == '':
        return target
    if isinstance(target, list):
        results = []
        targets = target
        for target in targets:
            func = eval(action)
            if hasattr(func, '__call__'):
                values = func(task, target)
            else:
                values = func
            if isinstance(values, list):
                for value in values:
                    result = format_element(value)
                    results.append(result)
            else:
                result = format_element(values)
                results.append(result)
    else:
        func = eval(action)
        if hasattr(func, '__call__'):
            values = func(task, target)
        else:
            values = func
        if isinstance(values, list):
            results = []
            for value in values:
                result = format_element(value)
                results.append(result)
        else:
            results = format_element(values)
    return results


# 根据xpath和sub_xpath提取元素
def get_element(selector, element_xpath, element_subpath):
    values = ''
    contents = []
    try:
        contents = selector.xpath(element_xpath)
    except Exception as e:
        logging.error(e)
        logging.error(traceback.format_exc())
        logging.error('deal xpath {0} subxpath {1} failed'.format(element_xpath, element_subpath))
    if len(contents) == 0:
        return values
    if element_subpath != '':
        for content in contents:
            sub_contents = content.xpath(element_subpath)
            if isinstance(sub_contents, list):
                values = []
                for sub_content in sub_contents:
                    result = format_element(sub_content)
                    if len(result) != 0:
                        values.append(result)
            else:
                values = format_element(sub_contents)
    else:
        if isinstance(contents, list):
            values = []
            for content in contents:
                result = format_element(content)
                if len(result) != 0:
                    values.append(result)
        else:
            values = format_element(contents)
    return values


# 根据加工的结果和下标 提取元素值
def get_element_value(results, element_index):
    element_value = ''
    # 字符串和列表需要处理， int不用处理，暂无dic之类的类型
    if isinstance(results, list) and len(results) != 0:
        if element_index != -1:
            if len(results) > element_index:
                element_value = format_element(results[element_index])
        else:
            element_value = []
            for result in results:
                value = format_element(result)
                if len(value) > 0:
                    element_value.append(value)
    elif isinstance(results, str) and len(results) != 0:
        element_value = format_element(results)
    return element_value


class ElementExtractor(object):
    def __init__(self, name, mdb):
        # 解析器名称，用于查找规则
        self.name = name
        # 规则存储的数据库
        self.mdb = mdb
        # 初始化时就提取规则
        self.rules = self.get_rules()
        # logging.info('self rules {0}'.format(self.rules[0][0]))

    def get_rules(self):
        rules = {}
        with self.mdb.my_conn.cursor() as cursor:
            try:
                sql = '''select seq, element_type, element, element_xpath,
                            element_subpath, `action`, element_index
                            from scrapy_rule where task='{0}'
                        '''.format(self.name)
                cursor.execute(sql)
                logging.info(sql)
                rows = cursor.fetchall()
                # logging.info('{0} rules fetched'.format(len(rows)))
            except Exception as e:
                logging.error(e)
                logging.error(traceback.format_exc())
                return rules

            for row in rows:
                seq = row[0]
                element_type = row[1]
                element = row[2]
                element_xpath = row[3]
                element_subpath = row[4]
                action = row[5]
                element_index = row[6]
                rules.setdefault(seq, {})
                rules[seq].setdefault(element_type, {})
                rules[seq][element_type].setdefault(element_xpath, {})
                rules[seq][element_type][element_xpath][element] = [element_subpath, action, element_index]
            logging.info('get {0} rules ok, {1} rules got'.format(self.name, len(rows)))
        return rules

    # 校验元素, 所有的校验元素都通过才算通过  element_type: 0普通元素 1校验预算 2保存键值元素
    def check_element(self, selector):
        ok_seq = 0
        ok_flag = False
        msg = ''
        for seq in self.rules:
            if consts['check_element_type'] in self.rules[seq]:
                check_elements = self.rules[seq][consts['check_element_type']]
                # logging.info(check_elements)
                check_flag = True
                for element_xpath in check_elements:
                    contents = selector.xpath(element_xpath)
                    # 一个失败即退出
                    if len(contents) == 0:
                        check_flag = False
                        # logging.warning('rules {0} check failed, no element {1}'.format(seq, element_xpath))
                        break
                if check_flag:
                    ok_seq = seq
                    ok_flag = True
                    break
            else:
                ok_seq = seq
                ok_flag = True
                break
        if not ok_flag:
            msg = 'no valid element {0}'.format(self.rules)
        return ok_seq, msg

    # 解析html文件元素
    def get_html_element(self, selector, task, seq=0):
        elements = {}
        # 普通元素  嵌套 seq-》type-》xpath-》element = [sub_xpath, action, index]
        # 可能只配置了校验规则，没有配置解析规则
        if consts['normal_element_type'] not in self.rules[seq]:
            return {}

        logging.debug('seq {0} {1}'.format(seq, consts['normal_element_type']))
        logging.debug('self rules {0}'.format(self.rules[0][0]))
        normal_elements = self.rules[seq][consts['normal_element_type']]
        logging.debug(normal_elements)
        for element_xpath in normal_elements:
            parents = []
            try:
                parents = selector.xpath(element_xpath)
            except Exception as e:
                logging.error(e)
                logging.error(traceback.format_exc())
                logging.error('deal task {0} xpath {1} failed'.format(task, element_xpath))
            if len(parents) == 0:
                continue
            if not isinstance(parents, list):
                parents = [parents]

            # 相同xpath下多个supxpath, 结果是列表,想要以整个单元存储, 需要一个指定名称
            store_unit_xpath = ''
            if consts['store_unit_type'] in self.rules[seq] \
                    and element_xpath in self.rules[seq][consts['store_unit_type']]:
                for element in self.rules[seq][consts['store_unit_type']][element_xpath]:
                    store_unit_xpath = element
                    break

            for parent in parents:
                # 遍历相同xpath下的各个元素
                element_dic = {}
                for element in normal_elements[element_xpath]:
                    element_subpath = normal_elements[element_xpath][element][0]
                    action = normal_elements[element_xpath][element][1]
                    element_index = normal_elements[element_xpath][element][2]
                    results = parents
                    if element_subpath != '':
                        # 根据subpath提取元素
                        results = get_element(parent, element_subpath, '')
                    # 提取出元素后 根据action进行加工
                    results = act_element_value(task, action, results)
                    # 加工完成后，根据下标获取元素
                    element_value = get_element_value(results, element_index)
                    if is_none(element_value):
                        logging.warning('element {0} not found in task {1}'.format(element, task))
                        continue

                    # 直接保存还是先存信息单元
                    if store_unit_xpath == '':
                        elements[element] = element_value
                    else:
                        element_dic[element] = element_value

                # 信息存储单元
                if store_unit_xpath != '':
                    if store_unit_xpath in elements:
                        elements[store_unit_xpath].append(element_dic)
                    else:
                        elements[store_unit_xpath] = [element_dic]
        return elements
